// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MefProvider.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   The bootstrapping of MEF requierd to launch in an Azure web or worker role.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.IO;
using System.Linq;

using AzureContrib.WindowsAzure.Properties;

namespace AzureContrib.WindowsAzure
{
	/// <summary>
	/// The bootstrapping of MEF requierd to launch in an Azure web or worker role.
	/// </summary>
	public class MefProvider
	{
		private readonly Action<string, string> nullEventLogMethod = (m, c) => { };
		private readonly Action<string, string> eventLogMethod;

		private readonly IEnumerable<ComposablePartCatalog> additionalComposablePartCatalogs;

		private CompositionContainer container;

		/// <summary>
		/// Initializes a new instance of the <see cref="MefProvider"/> class. 
		/// Constructs the Mef Provider class with an event log method to call for logging
		/// </summary>
		/// <param name="eventLogMethod">
		/// The event log method to call if this class needs logging.
		/// </param>
		/// Any additional <param name="additionalComposablePartCatalogs"> we need for composition.
		/// </param>
		public MefProvider(Action<string, string> eventLogMethod = null, IEnumerable<ComposablePartCatalog> additionalComposablePartCatalogs = null)
		{
			this.eventLogMethod = eventLogMethod ?? nullEventLogMethod;
			this.additionalComposablePartCatalogs = additionalComposablePartCatalogs ?? Enumerable.Empty<ComposablePartCatalog>();
		}

		private CompositionContainer Container
		{
			get { return container ?? (container = CreateContainer()); }
		}

		/// <summary>
		/// Run this to satisfy the imports on the <paramref name="part"/>.
		/// </summary>
		/// <param name="part">The part to satisfy imports on.</param>
		public void SatisfyImportsOnce(object part)
		{
			try
			{
				Container.SatisfyImportsOnce(part);
			}
			catch (Exception e)
			{
				eventLogMethod("Container import exception: {0}. Message <{1}>".F(e.GetType().FullName, e.Message), "Error");
				throw;
			}
		}

		/// <summary>
		/// Get an instance of a <typeparamref name="T"/>
		/// </summary>
		/// <typeparam name="T">The type of instance to get</typeparam>
		/// <returns>The Instance of <typeparamref name="T"/></returns>
		public T GetExportedValue<T>()
		{
			return Container.GetExportedValue<T>();
		}

		/// <summary>
		/// Get <see cref="Lazy{T}"/> of all exports of the type <typeparamref name="T"/>
		/// </summary>
		/// <typeparam name="T">The type of instance to get</typeparam>
		/// <returns>A lazy collection of instances of <typeparamref name="T"/></returns>
		public IEnumerable<Lazy<T>> GetExports<T>()
		{
			return Container.GetExports<T>();
		}

		private CompositionContainer CreateContainer()
		{
			string currentDirectory = Environment.CurrentDirectory;

			WriteToLog(string.Format(Strings.MEFCreatingCatalog, currentDirectory), "Information");

			var catalog = GetDirectoryCatalog(currentDirectory);

			if (additionalComposablePartCatalogs.Any())
			{
				var aggCatalog = new AggregateCatalog(additionalComposablePartCatalogs);
				aggCatalog.Catalogs.Add(catalog);
				catalog = aggCatalog;
			}

			var cont = new CompositionContainer(catalog);

			WriteToLog(Strings.MEFInitiationComplete, "Information");

			return cont;
		}

		private ComposablePartCatalog GetDirectoryCatalog(string currentDirectory)
		{
			// If there is a bin folder this is the web role and not the worker.
			var binFolder = Path.Combine(currentDirectory, "bin");

			return Directory.Exists(binFolder) ?
						 new DirectoryCatalog(binFolder) :
						 new DirectoryCatalog(currentDirectory);
		}

		private void WriteToLog(string message, string category)
		{
			eventLogMethod(message, category);
		}
	}
}